﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using Elderos.Utils.Logging;

namespace Elderos.Ontology
{
    [DataContract]
    public struct Date : IComparable<Date>, IComparable<DateTime>
    {
        [DataMember]
        public int? Year { get; private set; }
        [DataMember]
        public Month? Month { get; private set; }
        [DataMember]
        public byte? Day { get; private set; }

        public Date(int? year, Month? month, byte? day)
            : this()
        {
            if (day.HasValue)
            {
                Logger.Assert(() => day > 0 && day < 32, "Invalid day");

                if (month.HasValue && month.Value == Ontology.Month.February)
                    Logger.Assert(() => day < 30, "Invalid day");
                if (month.HasValue && new[]
                                          {
                                              Ontology.Month.April,
                                              Ontology.Month.June,
                                              Ontology.Month.September,
                                              Ontology.Month.November
                                          }.Contains(month.Value))
                    Logger.Assert(() => day < 31, "Invalid day value");
            }

            Logger.Assert(() => year.HasValue || month.HasValue || day.HasValue, "No data present in Elderos.Ontology.Date class.");

            Year = year;
            Month = month;
            Day = day;
        }

        public static Date Parse(string value)
        {
            Date date;
            if (TryParse(value, out date))
                return date;
            throw new FormatException();
        }

        public static bool TryParse(string value, out Date date)
        {
            Logger.Assert(() => value != null, "Cannot parse null value.");
            
            DateTime dt;
            if (DateTime.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.None, out dt))
            {
                date = new Date(dt.Year, (Month)dt.Month, (byte)dt.Day);
                return true;
            }
            int year;
            if (int.TryParse(value, out year))
            {
                date = new Date(year, null, null);
                return true;
            }

            if (value.Length == 7)
            {
                string yearString = value.Substring(0, 4);
                string monthString = value.Substring(5, 2);
                byte month;
                if (int.TryParse(yearString, out year) && byte.TryParse(monthString, out month))
                {
                    date = new Date(year, (Month)month, null);
                    return true;
                }
            }

            date = default(Date);
            return false;
        }

        #region Implementation of IComparable<in Date>

        /// <summary>
        /// Сравнивает текущий объект с другим объектом того же типа.
        /// </summary>
        /// <returns>
        /// Значение, указывающее, каков относительный порядок сравниваемых объектов.Расшифровка возвращенных значений приведена ниже.Значение Описание Меньше нуля Значение этого объекта меньше значения параметра <paramref name="other"/>.Нуль Значение этого объекта равно значению параметра <paramref name="other"/>. Больше нуля. Значение этого объекта больше значения параметра <paramref name="other"/>. 
        /// </returns>
        /// <param name="other">Объект, который требуется сравнить с данным объектом.</param>
        public int CompareTo(Date other)
        {
            if (this.Year.HasValue)
            {
                if (!other.Year.HasValue)
                    return 1;
                int yearResult = this.Year.Value.CompareTo(other.Year.Value);
                if (yearResult != 0)
                    return yearResult;
            }
            else if (other.Year.HasValue)
                return -1;

            if (this.Month.HasValue)
            {
                if (!other.Month.HasValue)
                    return 1;
                int monthResult = this.Month.Value.CompareTo(other.Month.Value);
                if (monthResult != 0)
                    return monthResult;
            }
            else if (other.Month.HasValue)
                return -1;

            if (this.Day.HasValue)
            {
                if (!other.Day.HasValue)
                    return 1;
                int dayResult = this.Day.Value.CompareTo(other.Day.Value);
                if (dayResult != 0)
                    return dayResult;
            }
            else if (other.Day.HasValue)
                return -1;

            return 0;
        }

        #endregion

        #region Implementation of IComparable<in DateTime>

        /// <summary>
        /// Сравнивает текущий объект с другим объектом того же типа.
        /// </summary>
        /// <returns>
        /// Значение, указывающее, каков относительный порядок сравниваемых объектов.Расшифровка возвращенных значений приведена ниже.Значение Описание Меньше нуля Значение этого объекта меньше значения параметра <paramref name="other"/>.Нуль Значение этого объекта равно значению параметра <paramref name="other"/>. Больше нуля. Значение этого объекта больше значения параметра <paramref name="other"/>. 
        /// </returns>
        /// <param name="other">Объект, который требуется сравнить с данным объектом.</param>
        public int CompareTo(DateTime other)
        {
            if (this.Year.HasValue)
            {
                int yearResult = this.Year.Value.CompareTo(other.Year);
                if (yearResult != 0)
                    return yearResult;
            }
            else return -1;

            if (this.Month.HasValue)
            {
                int monthResult = this.Month.Value.CompareTo((Month)other.Month);
                if (monthResult != 0)
                    return monthResult;
            }
            else return -1;

            if (this.Day.HasValue)
            {
                int dayResult = this.Day.Value.CompareTo((byte)other.Day);
                if (dayResult != 0)
                    return dayResult;
            }
            else return -1;

            return 0;
        }

        #endregion
    }
}
